package ge.lowlevel;




import java.util.ArrayList;

import ge.modules.logics.GeomCollideFigure;
import ge.modules.logics.SquareCollide;

public class CollisionEngine 
{
	private ArrayList<GeomCollideFigure> figures;
	
	private static CollisionEngine instance = null;
	private CollisionEngine()
	{
		figures = new ArrayList<GeomCollideFigure>();
	}
	
	/**
	 * @return The singleton instance of Event engine
	 */
	public static CollisionEngine get()
	{
		if(instance == null)
			instance = new CollisionEngine();
		return instance;
	}
	
	/**
	 * Remove all the figures attached to the engine.
	 * It's called when a level is changed.
	 */
	public void clear()
	{
		figures.clear();
	}
	/**
	 * 	Adds the figure to the Collision Engine in order to be tested with others figures
	 * @param figure GeometricFigure
	 */
	public void attachFigure(GeomCollideFigure figure)
	{
		figures.add(figure);
	}
	
	public boolean isAttached(GeomCollideFigure figure)
	{
		return figures.contains(figure);
	}
	/**
	 * 	Removes the figure of the attached figures
	 * @param figure	Figure to be removed
	 */
	public void removeFigure(GeomCollideFigure figure)
	{
		figures.remove(figure);
	}
	/**
	 * 	Makes the test collision with the figure and figures attached to the engine.
	 * 	
	 * @param figure Figure to be tested
	 * @param id	Identifier of the figures ar going to be tested. 0 means all of them.
	 * @return	Array List with the collision Vectors
	 */
	public ArrayList<Vector> collide(GeomCollideFigure figure, int id)
	{
		ArrayList<Vector> colisions = new ArrayList<Vector>();
		Vector v;
		for(GeomCollideFigure fg : figures)
		{
			if(fg != figure && ((id == 0 && fg.getId() >= 0 )|| fg.getId() == id))	//The same reference is the same figure
			{
				v = calculateCollide(figure, fg);
			 
				if(v != null && v.colision)
				{
					v.figure = fg;
					colisions.add(v);
				}
			}
		}
		return colisions;
	}
	/**
	 * 	Tests if two lines are overlapping and in what direction
	 * 	|-----------------|
	 *  iniA			 finA
	 *  	|------------------------|
	 *  	iniB					finB
	 * @param iniA
	 * @param finA
	 * @param iniB
	 * @param finB
	 * @return Direction
	 */
	private Double linesOverlap(Double iniA, Double finA, Double iniB , Double finB)
	{
		Double der = null,iz = null;
		if( iniA < iniB && iniB < finA)
			der = finA - iniB;
		
		if(iniA < finB && finB < finA)
			iz = iniA-finB;
		
		if(iz == null && der == null)
		{
			if((iniB <= iniA  && finA <= finB) || (iniB >= iniA  && finA >= finB) )
				return (double) 0;
			else
				return null;
		}
		else if(iz != null && der != null)
			return (double) 0;	//Interior
		else if(iz == null)
			return der;
		return iz;
	}
	
	/**
	 * 	Calculates the projection of the point x in the perpendicular to v1,v2 which trough the point x2,y2 
	 *  Needs the calculate point Y.
	 */
	private double proyeccionX(double v1, double v2, double x1,double y1, double x2, double y2, double y)
	{
		double x;
		if(v2 == 0)
			x = x1;
		else if(v1 == 0)
			x = x2;
		else
		{
			x = v1*y+x1*v2-y1*v1;
			x = x / v2;
		}
		
		return x;
	}
	/**
	 * 	Calculates the projection of the point Y in the perpendicular to v1,v2 which trough the point x2,y2 
	 */
	private double proyeccionY(double v1, double v2, double x1, double y1, double x2, double y2)
	{
		double y;
		 
		if(v2 == 0)
			y = y2;
		else if(v1 == 0)
			y = y1;
		else
		{
			double v1_2 = v1*v1;
			double v2_2 = v2*v2;

			y = -x1*v2*v1 +y1*v1_2 + x2*v1*v2 +  y2*v2_2;
			y =  y /(v1_2+v2_2);
		}
		return y;
	}
	
	/**
	 *	A1			 A2 *	B1			 B2
	 *  |------------|  *   |------------|
	 *(x,y)		  (x,y)	    (x,y)        (x,y)
	 */
	private Vector colisionProyeccion(double xA1,double yA1,double xA2,double yA2,double xB1,double yB1,double xB2,double yB2)
	{
		double p1x,p1y,p2x,p2y;
		 
		Vector m1 = new Vector();
		
		p1y = proyeccionY(yA2-yA1,xA2-xA1,xA1,yA1,xB1,yB1);
		p1x = proyeccionX(yA2-yA1,xA2-xA1,xA1,yA1,xB1,yB1,p1y);
		
		p2y = proyeccionY(yA2-yA1,xA2-xA1,xA1,yA1,xB2,yB2);
		p2x = proyeccionX(yA2-yA1,xA2-xA1,xA1,yA1,xB2,yB2,p2y);
		
		if(xA1 != xA2)	//Si las X no son iguales
		{
			m1.x = linesOverlap(xA1,xA2,p1x,p2x);
			m1.y = 0d;
			if(m1.x != null)
				m1.colision = true;
				
		}
		else	//Son horizontal, comprobamos que esta en medio por la componente y
		{
			m1.x = 0d;
			m1.y = linesOverlap(yA1,yA2,p1y,p2y);
			if(m1.y != null)
				m1.colision = true;
			
		}
		
		return m1;

	}
	
	/**
	 * 	Calculate the collision's vector of two shapes
	 * 	It really decides what is the adequate function for this calculation
	 * @param fA	Shape A
	 * @param fB	Shape B
	 * @return	Vector
	 */
	private Vector calculateCollide( GeomCollideFigure fA, GeomCollideFigure fB)
	{
		Vector normal = null;	//vector normal de la colision
		if(fA instanceof SquareCollide && fB instanceof SquareCollide)
			normal = collideSquares((SquareCollide)fA,(SquareCollide)fB);
		
		return normal;
	}
	
	/**
	 * 	Calculate Collide's vector of two squares
	 * @param fA	Square1 
	 * @param fB 	Square2
	 * @return	Vector
	 */
	private Vector collideSquares( SquareCollide fA, SquareCollide fB)
	{
		double  aAx,aAy,bAx,bAy,cAx,cAy;
		double  aBx,aBy,bBx,bBy,cBx,cBy;
		Vector m1,m2;
		//cuatro esquinas de A
		aAx = cAx = fA.getX();
		aAy = bAy = fA.getY();
		bAx = fA.getX()+fA.getWidth();
		cAy = fA.getY()+fA.getHeight();
		//cuatro esquinas de B
		aBx = cBx = fB.getX();
		aBy = bBy = fB.getY();
		bBx = fB.getX()+fB.getWidth();
		cBy = fB.getY()+fB.getHeight();
		

		//horizontal 
		m1 = colisionProyeccion(aAx,aAy,bAx,bAy,aBx,aBy,bBx,bBy);
		//console.log(fA.getId(),"m1",m1,fB.getId()	);
		if(!m1.colision)
			return m1;
		//vertical
		m2 = colisionProyeccion(aAx,aAy,cAx,cAy,aBx,aBy,cBx,cBy);
		if(!m2.colision)
			return m2;
		//console.log(fA.getId(),"m2",m2);
		
		//Se devuelve el que no es neutral, en caso de que no lo sea ambos, el menor
		if(m1.isNeutral())
			return m2;  
		else if(m2.isNeutral())
			return m1;
		if(m1.module() > m2.module())
			return m2;
		else 
			return m1;
	}

}
